package cn.demoncat.util.io;

import cn.demoncat.util.exception.IoRuntimeException;
import cn.demoncat.util.exception.ParamRuntimeException;
import cn.demoncat.util.lang.IDUtil;
import cn.demoncat.util.lang.ParamEmptyUtil;
import cn.demoncat.util.lang.StringUtil;
import cn.demoncat.util.lang.constant.CharsetConstant;
import cn.demoncat.util.lang.constant.StringConstant;
import cn.demoncat.util.web.UrlUtil;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLConnection;

/**
 * 文件名称工具：名称格式化、名称片段、名称匹配、文件编码
 * 
 * @author 延晓磊
 *
 * @since 2016年12月13日
 */
public final class FileNameUtil {

	// 文件名特殊字符
	private static final String[] ERROR_FILE_NAME = {"#",",","[","]","/","\\","?","=","&"};
	private static final ParamRuntimeException ERROR_FILE_NAME_EX = new ParamRuntimeException("文件名不允许包含特殊字符：# , ] [ / \\ ? = &");
	// 文件名长度
	private static final int MAX_FILE_NAME = 45;
	private static final ParamRuntimeException EMPTY_FILE_NAME_EX = new ParamRuntimeException("文件名不能为空");
	private static final ParamRuntimeException MAX_FILE_NAME_EX = new ParamRuntimeException("文件名长度不能超过45位");

	/**
	 * 文件名修剪（保证校验通过）
	 *
	 * @param fileName
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年12月13日
	 */
	public static String trim(String fileName){
		// 为空，随机名称
		if (StringUtils.isBlank(fileName)) {
			return String.valueOf(System.currentTimeMillis());
		}
		// 删除特殊符号
		fileName = StringUtil.removes(fileName, ERROR_FILE_NAME);
		// 长度剪切
		if (fileName.length() > MAX_FILE_NAME) {
			int index = fileName.lastIndexOf(StringConstant.POINT);
			if (index > 0 ) {
				String ext = fileName.substring(index);
				return fileName.substring(0, MAX_FILE_NAME - ext.length()) + ext;
			}else{
				return fileName.substring(0, MAX_FILE_NAME);
			}
		}
		return fileName;
	}

	/**
	 * 文件名修剪（保证校验通过）
	 *
	 * @param fileName
	 * @param url		文件路径名，如果fileName为空时从url截取
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年12月13日
	 */
	public static String trim(String fileName, String url){
		// 为空，从url截取
		if (StringUtils.isBlank(fileName)) {
			fileName = FileNameUtil.fileName(url, true);
		}
		// 删除特殊符号
		fileName = StringUtil.removes(fileName, ERROR_FILE_NAME);
		// 长度剪切
		if (fileName.length() > MAX_FILE_NAME) {
			int index = fileName.lastIndexOf(StringConstant.POINT);
			if (index > 0 ) {
				String ext = fileName.substring(index);
				return fileName.substring(0, MAX_FILE_NAME - ext.length()) + ext;
			}else{
				return fileName.substring(0, MAX_FILE_NAME);
			}
		}
		return fileName;
	}

	/**
	 * 文件名称校验
	 * 
	 * @param fileName
	 * @return 包含特殊字符/长度超限，返回 false
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年12月13日
	 */
	public static boolean check(String fileName) {
		if (StringUtils.isBlank(fileName) || fileName.length() > MAX_FILE_NAME) {
			return false;
		}
		for (String str : ERROR_FILE_NAME) {
			if (fileName.contains(str)) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * 文件名称强制校验
	 * 
	 * @param fileName
	 *
	 * @throws ParamRuntimeException 包含特殊字符/长度超限抛异常
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年12月13日
	 */
	public static void checkForce(String fileName) {
		if (StringUtils.isBlank(fileName)) {
			throw EMPTY_FILE_NAME_EX;
		}
		if (fileName.length() > MAX_FILE_NAME) {
			throw MAX_FILE_NAME_EX;
		}
		for (String str : ERROR_FILE_NAME) {
			if (fileName.contains(str)) {
				throw ERROR_FILE_NAME_EX;
			}
		}
	}

	/**
	 * 将文件路径名转换为指定type的规范路径名
	 * 
	 * @param fileName
	 * @param type 格式化类型：0 系统本地，1 Linux{/分隔符}，2 Windows{\分隔符}
	 * @return 格式化的标准路径名，fileName为空时返回""
	 */
	public static String fileNameFormat(String fileName, int type) {
		if (type != 0 && type != 1 && type != 2) {
			throw ParamRuntimeException.ILLEGAL;
		}
		if (StringUtils.isBlank(fileName)) {
			return StringConstant.EMPTY;
		}
		switch (type) {
		case 0:
			// 路径名分隔符使用本地系统的规则(自动辨认Linux/Windows)
			return FilenameUtils.separatorsToSystem(fileName);
		case 1:
			// 路径名分隔符使用Linux的规则：D:/d/a.txt
			return FilenameUtils.separatorsToUnix(fileName);
		case 2:
			// 路径名分隔符使用windows的规则：D:\d\a.txt
			return FilenameUtils.separatorsToWindows(fileName);
		default:
			return StringConstant.EMPTY;
		}
	}

	/**
	 * 自适应操作系统来获取文件名称，不包含目录
	 * 
	 * @param fileName
	 * @param hasExtension
	 *            是否包含文件扩展名
	 * @return 根据文件路径名获取文件名称，如果是目录则返回""
	 */
	public static String fileName(String fileName, boolean hasExtension) {
		if (hasExtension) {
			// 自动适应系统，通过文件路径获取文件名(/结尾返回""；非/结尾返回最后分隔的路径名)：a.txt
			return FilenameUtils.getName(fileName);
		}
		// 自动适应系统，通过文件路径获取文件名(/结尾返回""；非/结尾返回最后分隔的路径名，不包含文件扩展名；null返回null)：a
		return FilenameUtils.getBaseName(fileName);
	}

	/**
	 * 自适应操作系统来获取文件的目录名称，不包含末尾文件
	 * 
	 * @param fileName
	 * @param hasSeparator
	 *            是否包含末尾的目录分隔符
	 * @return 根据文件路径名获取文件的目录名称
	 */
	public static String dirName(String fileName, boolean hasSeparator) {
		if (hasSeparator) {
			// 自动适应系统，通过文件路径获取文件目录名(包含末尾分隔符，D:\da\xx\)
			return FilenameUtils.getFullPath(fileName);
		}
		// 自动适应系统，通过文件路径获取文件目录名(不包含末尾分隔符，D:\da\xx)
		return FilenameUtils.getFullPathNoEndSeparator(fileName);
	}

	/**
	 * 根据文件名获取扩展名，不包含.
	 * 
	 * @param fileName
	 * @return 如果没有扩展名则返回""，如果文件名为空返回null
	 */
	public static String getExtension(String fileName) {
		// 自动适应系统，通过文件名获取文件扩展名(最后一个.之后的名称，txt)
		return FilenameUtils.getExtension(fileName);
	}

	/**
	 * 设置文件扩展名
	 *
	 * @param ext
	 * @return .ext
	 */
	public static String setExtension(String ext){
		return StringUtils.isBlank(ext) ? StringConstant.EMPTY : (ext.startsWith(StringConstant.POINT) ? ext : StringConstant.POINT + ext);
	}

	/**
	 * 根据文件名获取扩展名，包含 .
	 * 
	 * @param fileName
	 * @return 如果没有扩展名则返回""，如果文件名为空返回null
	 */
	public static String getSuffix(String fileName) {
		if (StringUtils.isNotBlank(fileName)) {
			int index = fileName.lastIndexOf(StringConstant.POINT);
			if (index >= 0 ) {
				return fileName.substring(index);
			}
		}
		return StringConstant.EMPTY;
	}

	/**
	 * 获取文件名，不包含扩展名
	 *
	 * @param fileName
	 * @return 文件名
	 */
	public static String getName(String fileName) {
		if (StringUtils.isNotBlank(fileName)) {
			return FilenameUtils.getBaseName(fileName);
		}
		return StringConstant.EMPTY;
	}

	/**
	 * 拼接文件名和扩展名
	 * 
	 * @param fileName
	 * @param extension 后缀名，不包含.
	 * @return 如果文件名为空返回null，否则返回拼接后的名称
	 */
	public static String addExtension(String fileName, String extension) {
		if (StringUtils.isBlank(fileName)) {
			return null;
		}
		// 扩展名非空时拼接
		if (StringUtils.isNotBlank(extension)) {
			return fileName + StringConstant.POINT + extension;
		}
		return fileName;
	}

	/**
	 * 自适应操作系统来获取文件名的最后一个目录分隔符的索引
	 * 
	 * @param fileName
	 * @return 如果fileName为空或未找到则返回-1
	 */
	public static int lastSeparator(String fileName) {
		// 自动适应系统，并获取最后一个目录分隔符的索引：最后一个\
		return FilenameUtils.indexOfLastSeparator(fileName);
	}

	/**
	 * 自适应操作系统来获取文件名的最后一个扩展符 . 的索引
	 * 
	 * @param fileName
	 * @return 如果fileName为空或未找到则返回-1
	 */
	public static int lastExtension(String fileName) {
		// 自动适应系统，并获取最后一个扩展符的索引：最后一个.
		return FilenameUtils.indexOfExtension(fileName);
	}

	/**
	 * 检查文件名是否匹配matcher
	 * 
	 * @param fileName
	 * @param matcher	通配符表达式：* 多个任意字符，? 单个任意字符，如 "*.t?t"
	 * @return
	 */
	public static boolean isMatch(String fileName, String matcher) {
		if (ParamEmptyUtil.isBlank(fileName, matcher)) {
			return false;
		}
		// 检查文件名是否匹配指定的通配符表达式：* 多个任意字符；? 单个任意字符
		return FilenameUtils.wildcardMatch(fileName, matcher);
	}

	/**
	 * 检查文件名是否为指定扩展名的文件
	 * 
	 * @param fileName
	 * @param extensions
	 *            扩展名"txt","png"等
	 * @return
	 */
	public static boolean isExtension(String fileName, String... extensions) {
		if (StringUtils.isBlank(fileName) || extensions == null || extensions.length < 1) {
			return false;
		}
		// 检查文件是否为指定的扩展名的文件
		return FilenameUtils.isExtension(fileName, extensions);
	}

	/**
	 * 方法的描述: 判断文件的编码格式
	 * 
	 * @param file
	 * @return 字符编码
	 */
	public static String getCharset(File file) {
		if (file == null) {
			return null;
		}
		BufferedInputStream bin = null;
		try {
			bin = new BufferedInputStream(new FileInputStream(file));
			int p = (bin.read() << 8) + bin.read();
			String code;
			// 0xefbb、0xfffe、0xfeff、0x5c75是文件的前面两个字节的16进制数
			switch (p) {
			case 0xefbb:
				code = CharsetConstant.UTF8;
				break;
			case 0xfffe:
				code = CharsetConstant.UNICODE;
				break;
			case 0xfeff:
				code = CharsetConstant.UTF16;
				break;
			case 0x5c75:
				code = CharsetConstant.ASCII;
				break;
			default:
				code = CharsetConstant.GBK;
			}
			return code;
		} catch (IOException e) {
			throw new IoRuntimeException("判断文件的编码格式失败：" + e.getMessage(), e);
		} finally {
			CloseUtil.close(bin);
		}
	}
	
	/**
	 * 根据文件名，猜测MIME类型
	 * 
	 * @param name
	 * 
	 * @return mime类型
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年10月12日
	 */
	public static String getMimeType(String name) {
		if (StringUtils.isNotBlank(name)) {
			// 根据名称猜测
			name = URLConnection.getFileNameMap().getContentTypeFor(UrlUtil.encode(name));
		}
		if (StringUtils.isBlank(name)) {
			// 默认 octet-stream
			name = MediaType.APPLICATION_OCTET_STREAM_VALUE;
		}
		return name;
	}

	/**
	 * 获取上传文件名（统一文件名规范）
	 * 
	 * @param fileName	文件上传名
	 * 
	 * @return	/fileName
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年12月17日
	 */
	public static String getUploadName(String fileName) {
		if (fileName.startsWith(StringConstant.SEPARATOR)) {
			return fileName;
		}else {
			return StringConstant.SEPARATOR + fileName;
		}
	}
	
	/**
	 * 获取上传文件名（统一文件名规范）
	 * 
	 * @param url		图服URL
	 * @param fileName	文件上传名
	 * 
	 * @return url/fileName
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年12月17日
	 */
	public static String getUploadName(String url, String fileName) {
		boolean urlFlag = url.endsWith(StringConstant.SEPARATOR);
		if (fileName.startsWith(StringConstant.SEPARATOR)) {
			return url + (urlFlag ? fileName.substring(1) : fileName);
		}else {
			return url + (urlFlag ? fileName : StringConstant.SEPARATOR + fileName);
		}
	}

	/**
	 * 获取随机文件名
	 *
	 * @param fileName	文件原名，用于获取后缀
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2022年07月11日
	 */
	public static String getRandomName(String fileName){
		return IDUtil.getSnowflakeNumId() + FileNameUtil.getSuffix(fileName);
	}
	
}
